/**
   @file  win32.c
   @brief ENet Win32 system specific functions
 */
#if defined (_WINDOWS)

#include <time.h>
#define ENET_BUILDING_LIB 1
#include "enet/enet.h"

static enet_uint32 timeBase = 0;

int
enet_initialize()
{
    WORD versionRequested = MAKEWORD(1, 1);
    WSADATA wsaData;

    if (WSAStartup(versionRequested, &wsaData))
        return -1;

    if (LOBYTE(wsaData.wVersion) != 1 ||
        HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();

        return -1;
    }

    timeBeginPeriod(1);

    return 0;
}

void
enet_deinitialize()
{
    timeEndPeriod(1);

    WSACleanup();
}

enet_uint32
enet_time_get()
{
    return (enet_uint32)timeGetTime() - timeBase;
}

void
enet_time_set(enet_uint32 newTimeBase)
{
    timeBase = (enet_uint32)timeGetTime() - newTimeBase;
}

int
enet_address_set_host(ENetAddress* address, const char* name)
{
    struct hostent* hostEntry;

    hostEntry = gethostbyname(name);
    if (hostEntry == NULL ||
        hostEntry->h_addrtype != AF_INET)
    {
        unsigned long host = inet_addr(name);
        if (host == INADDR_NONE)
            return -1;
        address->host = host;
        return 0;
    }

    address->host = *(enet_uint32*)hostEntry->h_addr_list[0];

    return 0;
}

int
enet_address_get_host_ip(const ENetAddress* address, char* name, size_t nameLength)
{
    char* addr = inet_ntoa(*(struct in_addr*)&address->host);
    if (addr == NULL)
        return -1;
    strncpy(name, addr, nameLength);
    return 0;
}

int
enet_address_get_host(const ENetAddress* address, char* name, size_t nameLength)
{
    struct in_addr in;
    struct hostent* hostEntry;

    in.s_addr = address->host;

    hostEntry = gethostbyaddr((char*)&in, sizeof(struct in_addr), AF_INET);
    if (hostEntry == NULL)
        return enet_address_get_host_ip(address, name, nameLength);

    strncpy(name, hostEntry->h_name, nameLength);

    return 0;
}

int
enet_socket_bind(ENetSocket socket, const ENetAddress* address)
{
    struct sockaddr_in sin;

    memset(&sin, 0, sizeof(struct sockaddr_in));

    sin.sin_family = AF_INET;

    if (address != NULL)
    {
        sin.sin_port = ENET_HOST_TO_NET_16(address->port);
        sin.sin_addr.s_addr = address->host;
    }
    else
    {
        sin.sin_port = 0;
        sin.sin_addr.s_addr = INADDR_ANY;
    }

    return bind(socket,
                (struct sockaddr*)&sin,
                sizeof(struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
}

int
enet_socket_listen(ENetSocket socket, int backlog)
{
    return listen(socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0;
}

ENetSocket
enet_socket_create(ENetSocketType type)
{
    return socket(PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
}

int
enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value)
{
    int result = SOCKET_ERROR;
    switch (option)
    {
        case ENET_SOCKOPT_NONBLOCK:
        {
            u_long nonBlocking = (u_long)value;
            result = ioctlsocket(socket, FIONBIO, &nonBlocking);
            break;
        }

        case ENET_SOCKOPT_BROADCAST:
            result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof(int));
            break;

        case ENET_SOCKOPT_REUSEADDR:
            result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(int));
            break;

        case ENET_SOCKOPT_RCVBUF:
            result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&value, sizeof(int));
            break;

        case ENET_SOCKOPT_SNDBUF:
            result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(int));
            break;

        default:
            break;
    }
    return result == SOCKET_ERROR ? -1 : 0;
}

int
enet_socket_connect(ENetSocket socket, const ENetAddress* address)
{
    struct sockaddr_in sin;

    memset(&sin, 0, sizeof(struct sockaddr_in));

    sin.sin_family = AF_INET;
    sin.sin_port = ENET_HOST_TO_NET_16(address->port);
    sin.sin_addr.s_addr = address->host;

    return connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
}

ENetSocket
enet_socket_accept(ENetSocket socket, ENetAddress* address)
{
    SOCKET result;
    struct sockaddr_in sin = { 0 };
    int sinLength = sizeof(struct sockaddr_in);

    result = accept(socket,
                    address != NULL ? (struct sockaddr*)&sin : NULL,
                    address != NULL ? &sinLength : NULL);

    if (result == INVALID_SOCKET)
        return (ENetSocket)ENET_SOCKET_NULL;

    if (address != NULL)
    {
        address->host = (enet_uint32)sin.sin_addr.s_addr;
        address->port = ENET_NET_TO_HOST_16(sin.sin_port);
    }

    return result;
}

void
enet_socket_destroy(ENetSocket socket)
{
    closesocket(socket);
}

int
enet_socket_send(ENetSocket socket,
                 const ENetAddress* address,
                 const ENetBuffer* buffers,
                 size_t bufferCount)
{
    struct sockaddr_in sin;
    DWORD sentLength;

    if (address != NULL)
    {
        memset(&sin, 0, sizeof(struct sockaddr_in));

        sin.sin_family = AF_INET;
        sin.sin_port = ENET_HOST_TO_NET_16(address->port);
        sin.sin_addr.s_addr = address->host;
    }

    if (WSASendTo(socket,
                  (LPWSABUF)buffers,
                  (DWORD)bufferCount,
                  &sentLength,
                  0,
                  address != NULL ? (struct sockaddr*)&sin : 0,
                  address != NULL ? sizeof(struct sockaddr_in) : 0,
                  NULL,
                  NULL) == SOCKET_ERROR)
    {
        if (WSAGetLastError() == WSAEWOULDBLOCK)
            return 0;

        return -1;
    }

    return (int)sentLength;
}

int
enet_socket_receive(ENetSocket socket,
                    ENetAddress* address,
                    ENetBuffer* buffers,
                    size_t bufferCount)
{
    INT sinLength = sizeof(struct sockaddr_in);
    DWORD flags = 0,
          recvLength;
    struct sockaddr_in sin = { 0 };

    if (WSARecvFrom(socket,
                    (LPWSABUF)buffers,
                    (DWORD)bufferCount,
                    &recvLength,
                    &flags,
                    address != NULL ? (struct sockaddr*)&sin : NULL,
                    address != NULL ? &sinLength : NULL,
                    NULL,
                    NULL) == SOCKET_ERROR)
    {
        switch (WSAGetLastError())
        {
            case WSAEWOULDBLOCK:
            case WSAECONNRESET:
                return 0;
        }

        return -1;
    }

    if (flags & MSG_PARTIAL)
        return -1;

    if (address != NULL)
    {
        address->host = (enet_uint32)sin.sin_addr.s_addr;
        address->port = ENET_NET_TO_HOST_16(sin.sin_port);
    }

    return (int)recvLength;
}

int
enet_socketset_select(ENetSocket maxSocket, ENetSocketSet* readSet, ENetSocketSet* writeSet, enet_uint32 timeout)
{
    struct timeval timeVal;

    timeVal.tv_sec = timeout / 1000;
    timeVal.tv_usec = (timeout % 1000) * 1000;

    return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal);
}

int
enet_socket_wait(ENetSocket socket, enet_uint32* condition, enet_uint32 timeout)
{
    fd_set readSet, writeSet;
    struct timeval timeVal;
    int selectCount;

    timeVal.tv_sec = timeout / 1000;
    timeVal.tv_usec = (timeout % 1000) * 1000;

    FD_ZERO(&readSet);
    FD_ZERO(&writeSet);

    if (*condition & ENET_SOCKET_WAIT_SEND)
        FD_SET(socket, &writeSet);

    if (*condition & ENET_SOCKET_WAIT_RECEIVE)
        FD_SET(socket, &readSet);

    selectCount = select(socket + 1, &readSet, &writeSet, NULL, &timeVal);

    if (selectCount < 0)
        return -1;

    *condition = ENET_SOCKET_WAIT_NONE;

    if (selectCount == 0)
        return 0;

    if (FD_ISSET(socket, &writeSet))
        *condition |= ENET_SOCKET_WAIT_SEND;

    if (FD_ISSET(socket, &readSet))
        *condition |= ENET_SOCKET_WAIT_RECEIVE;

    return 0;
}

#endif